package com.zy.explore.client.infrastructure.config;

import com.zy.explore.client.infrastructure.base.jpa.BaseEntity;
import com.zy.explore.client.infrastructure.base.jpa.BaseJpaRepository;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.JpaRepository;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * description 充血模型配置类
 *
 * @author 🇨🇳中国印🇨🇳
 * @date 2022年04月17日 02:38:46
 */
@Slf4j
@Configuration
@ConditionalOnClass(JpaRepository.class)
public class JpaRichDomainConfig {

    public JpaRichDomainConfig(ObjectProvider<BaseJpaRepository<?, ?>> repositories) {
        Map<Class<?>, BaseJpaRepository<?, ?>> repositoryMap = repositories.orderedStream()
            .flatMap(repository -> Arrays.stream(repository.getClass().getInterfaces())
                .flatMap(repositoryType -> {
                    if (!repositoryType.isInterface()) {
                        return Stream.empty();
                    }

                    Type[] types = repositoryType.getGenericInterfaces();

                    if (types.length == 0) {
                        return Stream.empty();
                    }

                    for (Type type : types) {
                        if (type instanceof ParameterizedType) {
                            ParameterizedType parameterized = (ParameterizedType) type;
                            Type[] args = parameterized.getActualTypeArguments();

                            if (args.length > 0 && parameterized.getRawType() == BaseJpaRepository.class && args[0] instanceof Class<?>) {
                                return Stream.of(new Pair((Class<?>) args[0], repositoryType, repository));
                            }
                        }
                    }

                    return Stream.empty();
                })
                .peek(it -> log.info("Found repository {} for entity {}", it.getRepositoryType().getSimpleName(), it.getEntityType().getSimpleName())))
            .collect(Collectors.toMap(Pair::getEntityType, Pair::getRepository));

        BaseEntity.initRepositories(repositoryMap);
    }

    @Data
    private static final class Pair {

        private final Class<?> entityType;

        private final Class<?> repositoryType;

        private final BaseJpaRepository<?, ?> repository;
    }
}
